/* ===================================
Michal Kowalczyk
2010 EZI
==================================== */

#pragma once
// =====================================================
// Includes Event Manager, Render System and Base Logic
// =====================================================

// =====================================================
// EVENT MANAGER
// =====================================================

class EventManager
{
private:
	static EventManager * _instance;
	EventManager() {};
	~EventManager() {};
	EventManager(const EventManager &);             
	EventManager & operator=(const EventManager &); 
	//here add new signals/events
	boost::signal<void (int)> newActorSignal;
	boost::signal<void (char)> keyPressedSignal;
	boost::signal<CollisionInfo (int)> actorMovedSignal;
	boost::signal<void (int, int)> collisionSignal;
	boost::signal<void (int, int)> newMoveSignal;
public:
	static EventManager & Instance();

	bool setup();

	//here add new wrapper for an event
	void sendNewActor(int id);
	void sendKeyPressed(char key);
	CollisionInfo sendActorMoved(int id);
	void sendCollision(int id, int secondID);
	void sendNewMove(int id, int region);
};


// =====================================================
// RENDERER
// =====================================================
struct vector3D
{
	float x, y, z;
};

struct MOSDimensions
{
	int y1[7], y2[3], y3[1];
	MOSDimensions() {
		y1[0] = 104;

		y1[1] = 190;
		y1[2] = 146;
		y1[3] = 136;
		y1[4] = 62;
		y1[5] = 52;
		y1[6] = 8;

		y2[0] = 85;

		y2[1] = 126;
		y2[2] = 74;

		y3[0] = 32;
	}
};

struct Region
{
	float x[2];
	float z[2];
};

class MOS_Structure
{
private:
	int MOS_StructureList;
	int width;
	int height;
	MOSDimensions dim;
	float hfactor;
	vector3D **vertices;
	bool loadHeightMap(std::string filename, float heightFactor);
	STLogger & logger;
	std::string texturesNames[7];
	GLuint texture[7];
	int _x, _z;
	void drawSquare(int i, int j);
	void drawSquare(float x1, float x2, float z1, float z2);
	bool loadTextures();
public:
	Region ndoped_drn, ndoped_src, channel, under_channel, pdoped;
	Region drain, source, gate;
	MOS_Structure(): MOS_StructureList(0), width(0), height(0), hfactor(1.0f), logger(STLogger::Instance()) 
	{
		texturesNames[0] = "t1.jpg";
		texturesNames[1] = "t2.jpg";
		texturesNames[2] = "t3.jpg";
		texturesNames[3] = "t4.jpg";
		texturesNames[4] = "d.jpg";
		texturesNames[5] = "g.jpg";
		texturesNames[6] = "s.jpg";

		source.x[0] = 150.0f;
		source.x[1] = 45.0f;
		source.z[0] = 1.0f;
		source.z[1] = 38.0f;

		ndoped_src.x[0] = 140.0f;
		ndoped_src.x[1] = 45.0f;
		ndoped_src.z[0] = 50.0f;
		ndoped_src.z[1] = 30.0f;

		gate.x[0] = 65.0f;
		gate.x[1] = 70.0f;
		gate.z[0] = 1.0f;
		gate.z[1] = 20.0f;

		channel.x[0] = 80.0f;
		channel.x[1] = 45.0f;
		channel.z[0] = 50.0f;
		channel.z[1] = 10.0f;

		drain.x[0] = 12.0f;
		drain.x[1] = 35.0f;
		drain.z[0] = 1.0f;
		drain.z[1] = 40.0f;

		ndoped_drn.x[0] = 5.0f;
		ndoped_drn.x[1] = 45.0f;
		ndoped_drn.z[0] = 50.0f;
		ndoped_drn.z[1] = 30.0f;


		under_channel.x[0] = 80.0f;
		under_channel.x[1] = 40.0f;
		under_channel.z[0] = 65.0f;
		under_channel.z[1] = 20.0f;

		pdoped.x[0] = 5.0f;
		pdoped.x[1] = 185.0f;
		pdoped.z[0] = 95.0f;
		pdoped.z[1] = 30.0f;

	};
	~MOS_Structure()
	{
		for(int i = 0; i < width; i++)
		{
			delete [] vertices[i];
			vertices[i] = 0;
		}
		delete [] vertices;
		vertices = 0;

		glDeleteLists(MOS_StructureList, 1);
		//SDL_FreeSurface(texture);
	}
	bool setup();
	int getxMapSize() { return width; }
	int getzMapSize() { return height; }
	float getHFactor() { return hfactor; }
	void render()
	{
		glEnable(GL_TEXTURE_2D);
		glColor3f(1.0f, 1.0f, 1.0f);
		glCallList(MOS_StructureList);
		glDisable(GL_TEXTURE_2D);
	}
	float getHeight(float x, float z);
};

class Camera
{
private:
	friend class CameraListener;
	float x, y, z, hangle, vangle;
	int oldx, oldy;
	static bool autoMode;
	int dir;
	static bool releaseCursor;
	bool *m_keys;
	float speed, mouseSpeed;
	MOS_Structure * mos_structure;
public:
	inline Camera();
	~Camera() {};
	void setup(float s, float m, float startx, float startz, MOS_Structure * _MOS_Structure, bool * keys);
	void setup(float s, float m, MOS_Structure * _MOS_Structure, bool * keys);
	void update();
};

class CameraListener
{
public:
	void handleKeyPressedSignal(char key);
};

class HUD
{
private:
	//TODO: manage HUD objects
	TTF_Font* font;
	GLuint texture[4];
	SDL_Surface *intermediary;
	SDL_Surface *initial;
	SDL_Color color;
	float w,h, wgs, hgs, wds, hds, ws, hs;
	int screen_w, screen_h;
	float x, y, dsx, dsy, gsx, gsy, sx, sy;
	int round(double x)
	{
		return (int)(x + 0.5);
	}

	int nextpoweroftwo(int x)
	{
		double logbase2 = log((double)(x)) / log((double)2);
		return round(pow(2,ceil(logbase2)));
	}
	void glEnable2D()
	{
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		glOrtho(0, screen_w, 0, screen_h, -1, 1);
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();
	}

	void glDisable2D()
	{
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();   
		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();	
	}
public:
	HUD():screen_w(Core::getSettings().screen_width), screen_h(Core::getSettings().screen_height) {};
	~HUD() 
	{ 
		SDL_FreeSurface(intermediary); 	
		SDL_FreeSurface(initial);
	};
	void updateVgs();
	void updateVds();
	void updateState();
	bool setup();
	void renderLocation(const float & _x, const float & _y, const float & _w, const float & _h);
	void render();
};

class Renderer
{
private:
	static Renderer* _instance;
	friend class BaseLogic;
	EntityManager & m_entityManager;
	ViewMap m_viewMap;
	Camera * camera;
	HUD * hud;
	MOS_Structure * mos_structure;
	bool * m_keys;
	bool fog;
	float fogColor[4];
	void renderAllViews();
	Renderer(): fog(true), m_entityManager(EntityManager::Instance())
	{
		fogColor[0] = 0.6f;
		fogColor[1] = 0.4f;
		fogColor[2] = 0.7f;
		fogColor[3] = 1.0f;
	};
	~Renderer() 
	{
		delete mos_structure;
		delete camera;
	};
public:
	static Renderer & Instance();
	bool setup(bool * keys);
	void attachView(EntityID id, ViewPtr view);
	/*	ViewPtr getView(EntityID id)
	{
	return m_viewMap[id];
	}*/
	void update();
	MOS_Structure * getMOS_Structure() const 
	{ 
		return mos_structure; 
	};
	HUD * getHUD() const
	{
		return hud;
	};
};

class RendererListener
{
public:
	void handleKeyPressedSignal(char key);
};
// =====================================================
// MAIN LOGIC
// =====================================================
class InversionLayer;
class BaseLogic
{
private:
	static BaseLogic* _instance;
	EntityManager & entityManager;
	EventManager & eventManager;
	ProcessManager & processManager;
	int Vds;
	int Vgs;
	static const int e = 120;
	static const int h = 60;
	static const int n = e+h;
	int phase[n];
	Region ndoped_drn, ndoped_src, channel, under_channel, pdoped;
	Region drain, source, gate;
	float currWidthN;
	int state; // 0 - cut off, 1 - linear, 2 - saturation

	float x[n];
	float destX[n];
	int rx[n];
	float vx[n];

	float z[n];
	float destZ[n];
	int rz[n];
	float vz[n];

	float v;

	CUdeviceptr d_x;
	CUdeviceptr d_destX;
	CUdeviceptr d_rx;
	CUdeviceptr d_vx;

	CUdeviceptr d_z;
	CUdeviceptr d_destZ;
	CUdeviceptr d_rz;
	CUdeviceptr d_vz;

	// Variables
	CUdevice cuDevice;
	CUcontext cuContext;
	CUmodule cuModule;
	CUfunction parMoveX, parMoveZ;
	int devID;
	int threadsPerBlock;
	int blocksPerGrid; 
	CUresult error;
	CUresult CleanupNoFailure();
	int off1, off2;
	bool isCurrent;
	void Cleanup(bool);
	float current(int ugs, int uds);
	void setSpeed(float i);
	void updateStreams();
	bool startCUDA();

	boost::shared_ptr<InversionLayer> invlay;
	BaseLogic(): entityManager(EntityManager::Instance()), eventManager(EventManager::Instance()), \
		processManager(ProcessManager::Instance()), Vds(0), Vgs(0), v(1.0f), currWidthN(10.0f), isCurrent(false), \
		state(0) {
			for(int i = 0; i < n; i++)
			{
				phase[i] = 0;
				vx[i] = 1.0f;
				vz[i] = 1.0f;
			}
	}
	~BaseLogic() { 	Cleanup(true); };
public:
	static BaseLogic & Instance();
	int getVds() { return Vds; }
	int getVgs() { return Vgs; }
	int getState() { return state; }
	void increaseVgs();
	void decreaseVgs();
	void increaseVds();
	void decreaseVds();
	void updateState() { (void)current(Vgs, Vds); }
	bool setup();
	void update();
};

class LogicListener
{
private:
	ProcessManager & processManager;
	int mapWidth, mapHeight;
	int newX, newZ;
	float firstX, firstZ, secondX, secondZ;
	float silentX, silentZ;
	std::string firstType, secondType;
public:
	LogicListener(): processManager(ProcessManager::Instance()) 
	{
		mapWidth = Renderer::Instance().getMOS_Structure()->getxMapSize() - 2;
		mapHeight = Renderer::Instance().getMOS_Structure()->getzMapSize() - 2;
	};
	void handleCollisionSignal(int id, int secondID);
	void handleNewMoveSignal(int id, int region);
	void handleKeyPressedSignal(char key);
};

class MOS3DApp: public Core
{
private:
	// the internal reference to the entity manager
	// and the event manager 
	EntityManager & entityManager;
	EventManager & eventManager;

	// main process manager
	ProcessManager & processManager;

	// main logic 
	BaseLogic & baseLogic;

	// view system
	Renderer & renderer;

public:

	MOS3DApp(std::string appName, std::string appVersion): Core(appName, appVersion), \
		renderer(Renderer::Instance()), entityManager(EntityManager::Instance()), eventManager(EventManager::Instance()), \
		processManager(ProcessManager::Instance()), baseLogic(BaseLogic::Instance()) {};
	~MOS3DApp() {};
	void handleKeyUp(SDL_keysym* keysym);
	void handleKeyDown(SDL_keysym* keysym);
	// the main loop 
	void run();
};